package org.finesys.common.security.core.support;

import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletRequest;

import org.finesys.common.constants.SecurityConstants;
import org.finesys.common.security.core.config.AuthPermitAllUrlProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.BearerTokenErrors;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.PathMatcher;
import org.springframework.util.StringUtils;

public class AuthBearerTokenResolver implements BearerTokenResolver {

    /**
     * token正则校验
     */
    private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-:._~+/]+=*)$", Pattern.CASE_INSENSITIVE);

    private boolean allowFormEncodeBodyParameter = false;
    private boolean allowUriQueryParameter = true;

    private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;

    private final PathMatcher pathMatcher = new AntPathMatcher();


    private final AuthPermitAllUrlProperties authPermitAllUrlProperties;

    public AuthBearerTokenResolver(AuthPermitAllUrlProperties authPermitAllUrlProperties) {
        this.authPermitAllUrlProperties = authPermitAllUrlProperties;
    }

    @Override
    public String resolve(final HttpServletRequest request) {
        if (Objects.isNull(request)) {
            return null;
        }

        //校验URL
        boolean match = authPermitAllUrlProperties.getUrls().stream().anyMatch(url -> pathMatcher.match(url, request.getRequestURI()));
        if (match) {
            return null;
        }
        final String authorizationHeaderToken = resolveFromAuthrizationHeader(request);
        final String parameterToken = isParameterTokenSupportedForRequest(request) ? resolveFromRequestParameters(request) : null;
        if (authorizationHeaderToken != null && parameterToken != null) {
            final BearerTokenError bearerTokenError = BearerTokenErrors.invalidToken("Found multiple bearer tokens in request");
            throw new OAuth2AuthenticationException(bearerTokenError);
        }
        if (authorizationHeaderToken != null) {
            return authorizationHeaderToken;
        }
        if (parameterToken != null && isEnableParameterTokenSupportedForRequest(request)) {
            return parameterToken;
        }
        return null;
    }

    private String resolveFromAuthrizationHeader(HttpServletRequest request) {
        String authorization = request.getHeader(this.bearerTokenHeaderName);
        if (!StringUtils.startsWithIgnoreCase(authorization, SecurityConstants.OAUTH2_TOKEN_TYPE.trim())) {
            return null;
        }
        Matcher matcher = authorizationPattern.matcher(authorization);
        if (!matcher.matches()) {
            BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
            throw new OAuth2AuthenticationException(error);
        }
        return matcher.group(OAuth2ParameterNames.TOKEN);
    }

    private String resolveFromRequestParameters(HttpServletRequest request) {
        String[] values = request.getParameterValues(OAuth2ParameterNames.ACCESS_TOKEN);
        if (ObjectUtils.isEmpty(values)) {
            return null;
        }
        if (values.length == 1) {
            return values[0];
        }
        BearerTokenError bearerTokenError = BearerTokenErrors.invalidToken("Found multiple bearer tokens in the request");
        throw new OAuth2AuthenticationException(bearerTokenError);
    }

    private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {
        return ("POST".equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
                || "GET".equals(request.getMethod());
    }

    private boolean isEnableParameterTokenSupportedForRequest(final HttpServletRequest request) {
        return (this.allowFormEncodeBodyParameter && "POST".equals(request.getMethod()) && MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType()))
                || (this.allowUriQueryParameter && "GET".equals(request.getMethod()));
    }

    public boolean isAllowFormEncodeBodyParameter() {
        return allowFormEncodeBodyParameter;
    }

    public void setAllowFormEncodeBodyParameter(boolean allowFormEncodeBodyParameter) {
        this.allowFormEncodeBodyParameter = allowFormEncodeBodyParameter;
    }

    public boolean isAllowUriQueryParameter() {
        return allowUriQueryParameter;
    }

    public void setAllowUriQueryParameter(boolean allowUriQueryParameter) {
        this.allowUriQueryParameter = allowUriQueryParameter;
    }

    public String getBearerTokenHeaderName() {
        return bearerTokenHeaderName;
    }

    public void setBearerTokenHeaderName(String bearerTokenHeaderName) {
        this.bearerTokenHeaderName = bearerTokenHeaderName;
    }
}
